const router = require("koa-router")();
const jwt = require("jsonwebtoken");
const _ = require("lodash");
const util = require("./../util/util");
const Query = require("./../util/query");

router.post(
  "/class/:className/table/:table/(add|save|update)",
  async (ctx, next) => {
    const { className, table } = ctx.params;
    const fields = Object.assign(
      ctx.post,
      ctx.withFields(className, table, ctx.post)
    );

    if (!fields.id) {
      delete fields.id;
    }
    if (fields.created_at) {
      delete fields.created_at;
    }
    if (fields.updated_at) {
      delete fields.updated_at;
    }
    if (fields.deleted_at) {
      delete fields.deleted_at;
    }

    if (fields.id) {
      // 检测数据表是否可更新
      if (!ctx.classTable.update) {
        throw new Error(
          `Class ${className} Table ${table} No Permission Update`
        );
      }
    } else {
      // 检测数据表是否可新增
      if (!ctx.classTable.add) {
        throw new Error(`Class ${className} Table ${table} No Permission Add`);
      }
    }

    const key = `lock:add|save|update:class:${className}:table:${table}:url:${
      ctx.url
    }:post:${JSON.stringify(ctx.post)}:header:${JSON.stringify(ctx.headers)}`;
    const locked = await BaaS.redis.lock(key);
    if (locked) {
      const statement = [];
      if (!_.isEmpty(fields)) {
        // 当前tableInfo信息注入ctx
        const tableInfo = await BaaS.Models.table
          .query({
            where: {
              baas_id: ctx.baas.id,
              name: table
            }
          })
          .fetch({
            withRedisKey: ctx.getAppKey(`table:${table}`)
          });

        // model验证
        if (tableInfo) {
          // 唯一值验证
          if (tableInfo.unique) {
            const uniques = util.strToArray(tableInfo.unique);
            for (const key in uniques) {
              if (!fields[uniques[key]]) {
                continue;
              }

              const exist = await ctx
                .model(table)
                .query(qb => {
                  qb.where(uniques[key], fields[uniques[key]]);

                  if (fields.id) {
                    qb.where("id", "!=", fields.id);
                  }
                })
                .fetch({
                  withRedisKey: `fetch|fetchAll|fetchPage:class:${className}:table:${table}:url:${
                    ctx.url
                  }:header:${JSON.stringify(ctx.headers)}:unique:${
                    uniques[key]
                  }`
                });

              if (exist) {
                throw new Error(
                  `Class ${className} Table ${table} Unique ${uniques[key]}`
                );
              }
            }
          }
        }

        const result = await ctx
          .model(table)
          .forge(fields)
          .save({
            withRedisKey: `fetch|fetchAll|fetchPage:*:table:${table}:*`
          });

        ctx.success(result, "success");
      } else {
        throw new Error(
          `Class ${className} Table ${table} Post Add, Save, Update Data Can't Be Empty`
        );
      }

      await BaaS.redis.unlock(key);
    } else {
      console.log(`${key} Waiting`);
    }
  }
);

router.get(
  "/class/:className/table/:table/(del|delete|destroy)",
  async (ctx, next) => {
    const { className, table } = ctx.params;
    const { forceDelete = false } = ctx.query;
    const query = ctx.query;

    // 检测数据表是否可删除
    if (!ctx.classTable.delete) {
      throw new Error(`Class ${className} Table ${table} No Permission Delete`);
    }

    const key = `lock:del|delete|destroy:class:${className}:table:${table}:url:${
      ctx.url
    }:header:${JSON.stringify(ctx.headers)}`;
    const locked = await BaaS.redis.lock(key);
    if (locked) {
      let statement = [];
      const option = {};
      const { where, whereJson } = Query.toObject(query.where);
      const fields = ctx.withFields(className, table, whereJson);
      const result = await ctx
        .model(table)
        .query(qb => {
          if (!_.isEmpty(fields)) {
            qb.from(function() {
              this.where(fields);
              this.select()
                .from(`${ctx.baas.database}.${table}`)
                .as(table);
              statement = _.concat(statement, util.getStatement(this));
            });
          }

          // 合并where和whereJson
          const _whereJson = _.toPairs(whereJson).map(n => {
            return n.join();
          });
          if (_.isArray(where)) {
            query.where = where.concat(_whereJson);
          } else {
            query.where = where ? [where].concat(_whereJson) : _whereJson;
          }

          new Query(ctx, qb, query);
          statement = _.concat(statement, util.getStatement(this));
        })
        .fetchAll(option);

      if (result.length) {
        await BaaS.bookshelf.Collection.extend({
          model: ctx.model(table)
        })
          .forge(result)
          .invokeThen("destroy", {
            hardDelete: forceDelete,
            withRedisKey: `fetch|fetchAll|fetchPage:*:table:${table}:*`
          });

        ctx.success(result, "success", statement);
      } else {
        throw new Error(
          `Class ${className} Table ${table} Del, Delete, Destroy Data Not Found`
        );
      }

      await BaaS.redis.unlock(key);
    } else {
      console.log(`${key} Waiting`);
    }
  }
);

router.get(
  "/class/:className/table/:table/(fetch|fetchAll|fetchPage)",
  async (ctx, next) => {
    const { className, table } = ctx.params;
    const query = ctx.query;
    const fetchType = ctx.params[0];
    const {
      deleted = false,
      page = 1,
      pageSize = util.config("page.pageSize")
    } = ctx.query;

    // 检测数据表是否可查询
    if (!ctx.classTable.fetch) {
      throw new Error(`Class ${className} Table ${table} No Permission Fetch`);
    }

    let relateds = Query.getRelateds(query);
    // 过滤数组中的字符串
    const _relateds = [];
    for (const key in relateds) {
      if (_.isString(relateds[key])) {
        _relateds.push(relateds[key]);
        delete relateds[key];
      }
    }
    // 合并数组中同名对象
    relateds = _.reduceRight(relateds, (flattened, other) => {
      return _.merge(flattened, other);
    });

    let statement = [];
    const option = {
      withRelated: [].concat(_relateds, ctx.withRelateds(table, relateds)),
      withDeleted: deleted,
      withRedisKey: `fetch|fetchAll|fetchPage:class:${className}:table:${table}:url:${
        ctx.url
      }:header:${JSON.stringify(ctx.headers)}`
    };

    if (fetchType === "fetchPage") {
      option.page = page;
      option.pageSize = pageSize;
    }

    const key = `lock:fetch|fetchAll|fetchPage:class:${className}:table:${table}:url:${
      ctx.url
    }:header:${JSON.stringify(ctx.headers)}`;
    const locked = await BaaS.redis.lock(key);
    if (locked) {
      const { where, whereJson } = Query.toObject(query.where);
      const fields = ctx.withFields(className, table, whereJson);
      let result = await ctx
        .model(table)
        .query(qb => {
          if (!_.isEmpty(fields)) {
            qb.from(function() {
              this.where(fields);
              this.select()
                .from(`${ctx.baas.database}.${table}`)
                .as(table);
              statement = _.concat(statement, util.getStatement(this));
            });
          }

          // 合并where和whereJson
          const _whereJson = _.toPairs(whereJson).map(n => {
            return n.join();
          });
          if (_.isArray(where)) {
            query.where = where.concat(_whereJson);
          } else {
            query.where = where ? [where].concat(_whereJson) : _whereJson;
          }

          new Query(ctx, qb, query);
          statement = _.concat(statement, util.getStatement(qb));
        })
        [fetchType](option);

      // 加密数据
      try {
        if (ctx.classTable.secret) {
          result = jwt.sign(result, ctx.classTable.secret.secret, {
            expiresIn: ctx.classTable.secret.expires
          });
        }
      } catch (err) {
        if (!_.isEmpty(result)) {
          let type = typeof result;
          if (_.isArray(result)) {
            type = "Array";
          }
          // 部分数据不支持加密
          throw new Error(
            `Class ${className} Sign Secret Don't Support ${type}`
          );
        }
      }

      ctx.success(result, "success", {
        statement
      });

      await BaaS.redis.unlock(key);
    } else {
      console.log(`${key} Waiting`);
    }
  }
);

module.exports = router;
